package org.quickserver.net.client;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;









public class BlockingClient
  implements ClientService
{
  private static final Logger logger = Logger.getLogger(BlockingClient.class.getName());
  private static String charset = "ISO-8859-1";
  
  private static boolean debug = false;
  private static final int _CR = 13;
  private static final int _LF = 10;
  
  public static boolean isDebug() { return debug; }


  
  public static void setDebug(boolean aDebug) { debug = aDebug; }

  
  private String host = "localhost";
  private int port = 0;
  
  private Socket socket;
  
  private boolean secure;
  
  private boolean useDummyTrustManager;
  
  private TrustManager[] trustManager;
  
  private SSLContext sslContext;
  private SSLSocketFactory sslSocketFactory;
  private InputStream clientAuthKeystoreInputStream;
  private char[] clientAuthKeystorePassword;
  private char[] clientAuthKeyPassword;
  private OutputStream out;
  private BufferedOutputStream b_out;
  private ObjectOutputStream o_out;
  private InputStream in;
  private BufferedInputStream b_in;
  private BufferedReader br;
  private ObjectInputStream o_in;
  
  public void setCharset(String c) { charset = c; }

  
  public String getCharset() { return charset; }


  
  public int getMode() { return 1; }

  
  public void connect(String host, int port) throws Exception {
    this.host = host;
    this.port = port;
    
    if (isDebug()) logger.finest("Connecting to " + host + ":" + port);
    
    if (isSecure()) {
      makeSSLSocketFactory();
      this.socket = getSslSocketFactory().createSocket(host, port);
    } else {
      this.socket = new Socket(host, port);
    } 
    
    this.in = this.socket.getInputStream();
    this.out = this.socket.getOutputStream();
    if (isDebug()) logger.fine("Connected"); 
  }
  
  public boolean isConnected() {
    if (this.socket == null) return false; 
    return this.socket.isConnected();
  }
  
  public void close() throws IOException {
    if (isDebug()) logger.fine("Closing");
    
    if (this.out != null) {
      if (isDebug()) logger.finest("Closing output streams"); 
      try {
        this.out.flush();
      } catch (IOException ioe) {
        logger.log(Level.FINEST, "Flushing output streams failed: {0}", ioe);
      } 





      
      try {
        if (this.o_out != null) {
          this.o_out.close();
        }
      } catch (IOException ioe) {
        logger.log(Level.FINEST, "o_out stream close failed: {0}", ioe);
      } 
      
      try {
        if (this.b_out != null) {
          this.b_out.close();
        }
      } catch (IOException ioe) {
        logger.log(Level.FINEST, "b_out stream close failed: {0}", ioe);
      } 
      
      try {
        this.out.close();
      } catch (IOException ioe) {
        logger.log(Level.FINEST, "out stream close failed: {0}", ioe);
      } 
    } 
    
    if (this.in != null) {
      if (isDebug()) logger.finest("Closing input streams");





      
      if (this.o_in != null) {
        try {
          this.o_in.close();
        } catch (IOException ioe) {
          logger.log(Level.FINEST, "o_in stream close failed: {0}", ioe);
        } 
      }
      if (this.b_in != null) {
        try {
          this.b_in.close();
        } catch (IOException ioe) {
          logger.log(Level.FINEST, "b_in stream close failed: {0}", ioe);
        } 
      }
      if (this.br != null) {
        try {
          this.br.close();
        } catch (IOException ioe) {
          logger.log(Level.FINEST, "b_in stream close failed: {0}", ioe);
        } 
      }
      try {
        this.in.close();
      } catch (IOException ioe) {
        logger.log(Level.FINEST, "in stream close failed: {0}", ioe);
      } 
    } 
    
    if (this.socket != null) {
      this.socket.close();
      this.socket = null;
    } 
  }
  
  public void sendByte(int data) throws IOException {
    if (isDebug()) logger.fine("Sending byte"); 
    checkBufferedOutputStream();
    this.b_out.write(data);
    this.b_out.flush();
  }
  
  public void sendBytes(byte[] data) throws IOException {
    if (isDebug()) logger.log(Level.FINE, "Sending bytes: {0}", Integer.valueOf(data.length)); 
    checkBufferedOutputStream();
    this.b_out.write(data);
    this.b_out.flush();
  }
  
  public void sendBytes(String data, String _charset) throws IOException {
    if (isDebug()) logger.log(Level.FINE, "Sending: {0}", data); 
    checkBufferedOutputStream();
    if (_charset == null) _charset = charset; 
    byte[] d = data.getBytes(_charset);
    this.b_out.write(d, 0, d.length);
    this.b_out.flush();
  }
  
  public void sendLine(String data, String _charset) throws IOException {
    if (isDebug()) logger.log(Level.FINE, "Sending: {0}", data); 
    checkBufferedOutputStream();
    if (_charset == null) _charset = charset; 
    byte[] d = data.getBytes(_charset);
    this.b_out.write(d, 0, d.length);
    d = "\r\n".getBytes(_charset);
    this.b_out.write(d, 0, d.length);
    this.b_out.flush();
  }
  
  public void sendObject(Object data) throws IOException {
    checkObjectOutputStream();
    this.o_out.writeObject(data);
    this.o_out.flush();
  }
  
  public int readByte() throws IOException {
    checkBufferedInputStream();
    return this.b_in.read();
  }
  
  public byte[] readBytes() throws IOException {
    checkBufferedInputStream();
    return readInputStream(this.b_in);
  }
  
  public byte[] readBytes(int countToRead) throws IOException {
    checkBufferedInputStream();
    return readInputStream(this.b_in, countToRead);
  }
  
  public String readBytes(String _charset) throws IOException {
    byte[] data = readBytes();
    if (data == null) return null; 
    if (_charset == null) _charset = charset; 
    return new String(data, _charset);
  }
  
  public String readBytes(String _charset, int countToRead) throws IOException {
    byte[] data = readInputStream(this.b_in, countToRead);
    if (data == null) return null; 
    if (_charset == null) _charset = charset; 
    return new String(data, _charset);
  }
  
  public String readLine() throws IOException {
    checkBufferedReader();
    return this.br.readLine();
  }
  
  public String readCRLFLine() throws IOException {
    checkBufferedInputStream();
    
    StringBuilder sb = new StringBuilder();
    int _ch = -1;
    while (true) {
      _ch = this.b_in.read();
      if (_ch == -1) {
        return null;
      }
      if (_ch == 13) {
        _ch = this.b_in.read();
        if (_ch == 10) {
          break;
        }
        sb.append('\r');
        sb.append((char)_ch);
        continue;
      } 
      sb.append((char)_ch);
    } 

    
    return new String(sb);
  }
  
  public Object readObject() throws IOException, ClassNotFoundException {
    checkObjectInputStream();
    return this.o_in.readObject();
  }

  
  public Socket getSocket() { return this.socket; }

  
  private void checkObjectOutputStream() throws IOException {
    if (this.o_out == null) {
      this.b_out = null;
      this.o_out = new ObjectOutputStream(this.out);
      this.o_out.flush();
    } 
  }
  private void checkBufferedOutputStream() throws IOException {
    if (this.b_out == null) {
      this.o_out = null;
      this.b_out = new BufferedOutputStream(this.out);
    } 
  }
  
  private void checkBufferedInputStream() throws IOException {
    if (this.b_in == null) {
      this.br = null;
      this.o_in = null;
      this.b_in = new BufferedInputStream(this.in);
    } 
  }
  private void checkBufferedReader() throws IOException {
    if (this.br == null) {
      this.b_in = null;
      this.o_in = null;
      this.br = new BufferedReader(new InputStreamReader(this.in, charset));
    } 
  }
  private void checkObjectInputStream() throws IOException {
    if (this.o_in == null) {
      this.b_in = null;
      this.br = null;
      this.o_in = new ObjectInputStream(this.in);
    } 
  }

  
  public static byte[] readInputStream(InputStream _in, int countToRead) throws IOException {
    byte[] data = new byte[countToRead];
    if (_in == null) {
      throw new IOException("InputStream can't be null!");
    }
    
    int count = 0;
    int dataRead = 0;
    int dataLeftToRead = countToRead - dataRead;
    
    do {
      count = _in.read(data, dataRead, dataLeftToRead);
      if (count == -1) {
        if (dataRead == countToRead) {
          break;
        }
        throw new IOException("we have eof!");
      } 
      
      dataRead += count;
      dataLeftToRead = countToRead - dataRead;
    
    }
    while (dataRead < countToRead);


    
    return data;
  }
  
  public static byte[] readInputStream(InputStream _in) throws IOException {
    byte[] data = null;
    if (_in == null) {
      throw new IOException("InputStream can't be null!");
    }
    int s = _in.read();
    if (s == -1) {
      return null;
    }
    int alength = _in.available();
    if (alength > 0) {
      data = new byte[alength + 1];
      data[0] = (byte)s;
      int len = _in.read(data, 1, alength);
      if (len < alength) {
        data = copyOf(data, len + 1);
      }
    } else {
      data = new byte[1];
      data[0] = (byte)s;
    } 
    return data;
  }
  
  private static byte[] copyOf(byte[] data, int len) {
    byte[] newdate = new byte[len];
    System.arraycopy(data, 0, newdate, 0, len);
    return newdate;
  }

  
  public boolean isSecure() { return this.secure; }


  
  public void setSecure(boolean secure) { this.secure = secure; }


  
  public boolean isUseDummyTrustManager() { return this.useDummyTrustManager; }


  
  public void setUseDummyTrustManager(boolean useDummyTrustManager) { this.useDummyTrustManager = useDummyTrustManager; }


  
  public TrustManager[] getTrustManager() { return this.trustManager; }


  
  public void setTrustManager(TrustManager[] trustManager) { this.trustManager = trustManager; }


  
  public SSLContext getSslContext() { return this.sslContext; }


  
  public void setSslContext(SSLContext sslContext) { this.sslContext = sslContext; }


  
  public SSLSocketFactory getSslSocketFactory() { return this.sslSocketFactory; }


  
  public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) { this.sslSocketFactory = sslSocketFactory; }

  
  public void makeSSLSocketFactory() throws Exception {
    if (getSslContext() == null && getSslSocketFactory() == null) {
      SSLContext context = SSLContext.getInstance("SSLv3");
      if (getTrustManager() == null && isUseDummyTrustManager()) {
        setTrustManager(new TrustManager[] { DummyTrustManager.getInstance() });
      }
      
      KeyManager[] km = null;
      if (getClientAuthKeystoreInputStream() != null) {
        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(getClientAuthKeystoreInputStream(), getClientAuthKeystorePassword());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, getClientAuthKeyPassword());
        km = kmf.getKeyManagers();
      } else {
        km = new KeyManager[0];
      } 
      
      context.init(km, getTrustManager(), new SecureRandom());
      setSslContext(context);
    } 
    
    if (getSslSocketFactory() == null) {
      SSLSocketFactory factory = getSslContext().getSocketFactory();
      setSslSocketFactory(factory);
    } 
  }

  
  public InputStream getClientAuthKeystoreInputStream() { return this.clientAuthKeystoreInputStream; }


  
  public void setClientAuthKeystoreInputStream(InputStream clientAuthKeystoreInputStream) { this.clientAuthKeystoreInputStream = clientAuthKeystoreInputStream; }


  
  public char[] getClientAuthKeystorePassword() { return this.clientAuthKeystorePassword; }


  
  public void setClientAuthKeystorePassword(char[] clientAuthKeystorePassword) { this.clientAuthKeystorePassword = clientAuthKeystorePassword; }


  
  public char[] getClientAuthKeyPassword() { return this.clientAuthKeyPassword; }


  
  public void setClientAuthKeyPassword(char[] clientAuthKeyPassword) { this.clientAuthKeyPassword = clientAuthKeyPassword; }
}
